1、前言 springcloud是基于springboot开发的,所以读者在阅读此文前最好已经了解了springboot的工作原理 。本文将不阐述springboot的工作逻辑。
在整个 Spring Boot 启动的生命周期过程中,有一个阶段是 prepare environment。在这个阶段,会publish 一个 ApplicationEnvironmentPreparedEvent
,通知所有对这个事件感兴趣的 Listener,提供对 Environment 做更多的定制化的操作。Spring Cloud 定义了一个BootstrapApplicationListener
,在 BootstrapApplicationListener
的处理过程中会创建spring cloud的ApplicationContext。
2、spring cloud context springboot cloud context在官方的文档中在第一点被提及,是用户ApplicationContext的父级上下文,笔者称呼为BootstrapContext 。根据springboot的加载机制,很多第三方以及重要的Configuration配置均是保存在了spring.factories 文件中。
笔者翻阅了spring-cloud-context 模块下的对应文件,见如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 org.springframework.boot.autoconfigure.EnableAutoConfiguration =\ org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\ org.springframework.cloud.autoconfigure.LifecycleMvcEndpointAutoConfiguration,\ org.springframework.cloud.autoconfigure.RefreshAutoConfiguration,\ org.springframework.cloud.autoconfigure.RefreshEndpointAutoConfiguration,\ org.springframework.cloud.autoconfigure.WritableEnvironmentEndpointAutoConfiguration org.springframework.context.ApplicationListener =\ org.springframework.cloud.bootstrap.BootstrapApplicationListener,\ org.springframework.cloud.bootstrap.LoggingSystemShutdownListener,\ org.springframework.cloud.context.restart.RestartListener org.springframework.cloud.bootstrap.BootstrapConfiguration =\ org.springframework.cloud.bootstrap.config.PropertySourceBootstrapConfiguration,\ org.springframework.cloud.bootstrap.encrypt.EncryptionBootstrapConfiguration,\ org.springframework.cloud.autoconfigure.ConfigurationPropertiesRebinderAutoConfiguration,\ org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration
涉及的主要分三类,笔者优先分析监听器 ,其一般拥有更高的优先级并跟其他两块有一定的关联性。 除了日志监听器笔者不太关注,其余两个分步骤来分析
2.1、RestartListener 重启监听器,应该是用于刷新上下文的,直接查看下其复写的方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public class RestartListener implements SmartApplicationListener { private ConfigurableApplicationContext context; private ApplicationPreparedEvent event; @Override public void onApplicationEvent (ApplicationEvent input) { if (input instanceof ApplicationPreparedEvent) { this .event = (ApplicationPreparedEvent) input; if (this .context == null ) { this .context = this .event.getApplicationContext(); } } else if (input instanceof ContextRefreshedEvent) { if (this .context != null && input.getSource().equals(this .context) && this .event != null ) { this .context.publishEvent(this .event); } } else { if (this .context != null && input.getSource().equals(this .context)) { this .context = null ; this .event = null ; } } } }
上述的刷新事件经过查阅,与org.springframework.cloud.context.restart.RestartEndpoint 类有关,这个就后文再分析好了
2.2、BootstrapApplicationListener 按照顺序分析此监听器
1、优先看下其类结构
1 2 3 public class BootstrapApplicationListener implements ApplicationListener <ApplicationEnvironmentPreparedEvent >, Ordered {}
此监听器是用于响应ApplicationEnvironmentPreparedEvent 应用环境变量预初始化事件,表明BootstrapContext的加载时机在用户上下文(spring boot的ApplicationContext)之前 ,且其加载顺序比ConfigFileApplicationListener监听器超前 ,这点稍微强调下,监听器的具体执行顺序见我的另一篇文章Spring Boot启动流程分析 。
2、接下来分析下其复写的方法*onApplicationEvent(ApplicationEnvironmentPreparedEvent event)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 @Override public void onApplicationEvent (ApplicationEnvironmentPreparedEvent event) { ConfigurableEnvironment environment = event.getEnvironment(); if (!environment.getProperty("spring.cloud.bootstrap.enabled" , Boolean.class , true )) { return ; } if (environment.getPropertySources().contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) { return ; } ConfigurableApplicationContext context = null ; String configName = environment.resolvePlaceholders("${spring.cloud.bootstrap.name:bootstrap}" ); for (ApplicationContextInitializer initializer : event.getSpringApplication() .getInitializers()) { if (initializer instanceof ParentContextApplicationContextInitializer) { context = findBootstrapContext( (ParentContextApplicationContextInitializer) initializer, configName); } } if (context == null ) { context = bootstrapServiceContext(environment, event.getSpringApplication(), configName); event.getSpringApplication() .addListeners(new CloseContextOnFailureApplicationListener(context)); } apply(context, event.getSpringApplication(), environment); } private void apply (ConfigurableApplicationContext context, SpringApplication application, ConfigurableEnvironment environment) { @SuppressWarnings ("rawtypes" ) List initializers = getOrderedBeansOfType(context, ApplicationContextInitializer.class ) ; application.addInitializers(initializers .toArray(new ApplicationContextInitializer[initializers.size()])); addBootstrapDecryptInitializer(application); }
逻辑很简单,上面的代码注释已经给出每一步详细说明,这里再梳理下:
spring.cloud.bootstrap.enabled 用于配置是否启用BootstrapContext,默认为true。可通过命令行参数、系统属性、系统环境变量设定
spring.cloud.bootstrap.name 用于加载bootstrap对应配置文件的别名,默认为bootstrap,在ConfigFileApplicationListener事件中会使用该名称加载配置文件
BootstrapContext上的beanType为ApplicationContextInitializer 类型的bean对象集合会被注册至用户的Context上
3、重点看下BootstrapContext的创建过程,源码比较长
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 private ConfigurableApplicationContext bootstrapServiceContext ( ConfigurableEnvironment environment, final SpringApplication application, String configName) { StandardEnvironment bootstrapEnvironment = new StandardEnvironment(); MutablePropertySources bootstrapProperties = bootstrapEnvironment.getPropertySources(); for (PropertySource source : bootstrapProperties) { bootstrapProperties.remove(source.getName()); } String configLocation = environment.resolvePlaceholders("${spring.cloud.bootstrap.location:}" ); Map bootstrapMap = new HashMap<>(); bootstrapMap.put("spring.config.name" , configName); bootstrapMap.put("spring.main.web-application-type" , "none" ); if (StringUtils.hasText(configLocation)) { bootstrapMap.put("spring.config.location" , configLocation); } bootstrapProperties.addFirst(new MapPropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME, bootstrapMap)); for (PropertySource source : environment.getPropertySources()) { if (source instanceof StubPropertySource) { continue ; } bootstrapProperties.addLast(source); } SpringApplicationBuilder builder = new SpringApplicationBuilder() .profiles(environment.getActiveProfiles()) .bannerMode(Mode.OFF) .environment(bootstrapEnvironment) .registerShutdownHook(false ).logStartupInfo(false ) .web(WebApplicationType.NONE); final SpringApplication builderApplication = builder.application(); if (builderApplication.getMainApplicationClass() == null ) { builder.main(application.getMainApplicationClass()); } if (environment.getPropertySources().contains("refreshArgs" )) { builderApplication.setListeners(filterListeners(builderApplication.getListeners())); } builder.sources(BootstrapImportSelectorConfiguration.class ) ; final ConfigurableApplicationContext context = builder.run(); context.setId("bootstrap" ); addAncestorInitializer(application, context); bootstrapProperties.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME); mergeDefaultProperties(environment.getPropertySources(), bootstrapProperties); return context; } private void mergeDefaultProperties (MutablePropertySources environment, MutablePropertySources bootstrap) { String name = DEFAULT_PROPERTIES; if (bootstrap.contains(name)) { PropertySource source = bootstrap.get(name); if (!environment.contains(name)) { environment.addLast(source); } else { PropertySource target = environment.get(name); if (target instanceof MapPropertySource && target != source && source instanceof MapPropertySource) { Map targetMap = ((MapPropertySource) target).getSource(); Map map = ((MapPropertySource) source).getSource(); for (String key : map.keySet()) { if (!target.containsProperty(key)) { targetMap.put(key, map.get(key)); } } } } } mergeAdditionalPropertySources(environment, bootstrap); }
此处也对上述的代码作下简单的小结:
spring.cloud.bootstrap.location 变量用于配置bootstrapContext配置文件的加载路径,可用System设置,默认则采取默认的文件搜寻路径;与spring.cloud.bootstrap.name 搭配使用
bootstrapContext对应的activeProfiles可采用spring.active.profiles 系统变量设置,注意是System变量。当然也可以通过bootstrap.properties/bootstrap.yml 配置文件设置
bootstrapContext的重要入口类为BootstrapImportSelectorConfiguration ,此也是下文的分析重点
bootstrapContext的contextId为bootstrap 。即使配置了spring.application.name 属性也会被设置为前者,且其会被设置为用户Context的父上下文
bootstrap.(yml | properties)上的配置会被合并至用户级别的Environment中的defaultProperties 集合中,且其相同的KEY会被丢弃,不同KEY会被保留。即其有最低的属性优先级
通过上述的代码均可以得知,bootstrapContext也是通过springboot常见的SpringApplication
方式来创建的,但其肯定有特别的地方。 特别之处就在BootstrapImportSelectorConfiguration 类,其也与上述spring.factories
文件中org.springframework.cloud.bootstrap.BootstrapConfiguration 的Key有直接的关系,我们下文重点分析。
2.3、BootstrapImportSelector 首先回顾bootstrapServiceContext()方法中的BootstrapImportSelectorConfiguration设置过程。
1 2 3 4 5 6 7 8 private ConfigurableApplicationContext bootstrapServiceContext () { builder.sources(BootstrapImportSelectorConfiguration.class ) ; }
承接前文监听器对bootstrapContext创建的引导,可以看到到其主要入口类为BootstrapImportSelectorConfiguration
。下文将基于此类进行简单的分析。
其源码如下:
1 2 3 4 5 @Configuration (proxyBeanMethods = false )@Import (BootstrapImportSelector.class ) public class BootstrapImportSelectorConfiguration {}
嗯,引入了延迟加载类BootstrapImportSelector ,那就继续往下看下此会延迟加载哪些类,直接去观察其主方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 public class BootstrapImportSelector implements EnvironmentAware , DeferredImportSelector { private Environment environment; private MetadataReaderFactory metadataReaderFactory = new CachingMetadataReaderFactory(); @Override public void setEnvironment (Environment environment) { this .environment = environment; } @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); List names = new ArrayList<>(SpringFactoriesLoader.loadFactoryNames(BootstrapConfiguration.class , classLoader )) ; names.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray( this .environment.getProperty("spring.cloud.bootstrap.sources" , "" )))); List elements = new ArrayList<>(); for (String name : names) { try { elements.add( new OrderedAnnotatedElement(this .metadataReaderFactory, name)); } catch (IOException e) { continue ; } } AnnotationAwareOrderComparator.sort(elements); String[] classNames = elements.stream().map(e -> e.name).toArray(String[]::new ); return classNames; } }
上述的代码很简单,其会去加载classpath路径下所有spring.factories 文件中以org.springframework.cloud.bootstrap.BootstrapConfiguration 作为Key的所有类; 同时springcloud也支持通过设置spring.cloud.bootstrap.sources 属性来加载指定类。
下面就先以springcloud context板块内的spring.factories 作为分析的源头
在分析上述的源码之前,笔者必须得清楚现在bootstrapContext加载的配置文件默认为bootstrap.properties抑或是bootstrap.yml,其属性已经被加入至相应的Environment对象中 。基于此我们再往下走,以免犯糊涂。
2.3.1、PropertySourceBootstrapConfiguration 配置源的加载 ,此Configuration主要用于确定是否外部加载的配置属性复写Spring内含的环境变量。注意其是ApplicationContextInitializer 接口的实现类,前文已经提到,bootstrapContext上的此接口的bean类都会被注册至子级的SpringApplication对象上 。 直接看下主要的代码片段吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 @Configuration (proxyBeanMethods = false )@EnableConfigurationProperties (PropertySourceBootstrapProperties.class ) public class PropertySourceBootstrapConfiguration implements ApplicationContextInitializer <ConfigurableApplicationContext >, Ordered { public static final String BOOTSTRAP_PROPERTY_SOURCE_NAME = BootstrapApplicationListener.BOOTSTRAP_PROPERTY_SOURCE_NAME + "Properties" ; private int order = Ordered.HIGHEST_PRECEDENCE + 10 ; @Autowired (required = false ) private List propertySourceLocators = new ArrayList<>(); public void setPropertySourceLocators (Collection propertySourceLocators) { this .propertySourceLocators = new ArrayList<>(propertySourceLocators); } @Override public void initialize (ConfigurableApplicationContext applicationContext) { CompositePropertySource composite = new OriginTrackedCompositePropertySource(BOOTSTRAP_PROPERTY_SOURCE_NAME); AnnotationAwareOrderComparator.sort(this .propertySourceLocators); boolean empty = true ; ConfigurableEnvironment environment = applicationContext.getEnvironment(); for (PropertySourceLocator locator : this .propertySourceLocators) { PropertySource source = null ; source = locator.locate(environment); if (source == null ) { continue ; } logger.info("Located property source: " + source); composite.addPropertySource(source); empty = false ; } if (!empty) { MutablePropertySources propertySources = environment.getPropertySources(); String logConfig = environment.resolvePlaceholders("${logging.config:}" ); LogFile logFile = LogFile.get(environment); if (propertySources.contains(BOOTSTRAP_PROPERTY_SOURCE_NAME)) { propertySources.remove(BOOTSTRAP_PROPERTY_SOURCE_NAME); } insertPropertySources(propertySources, composite); reinitializeLoggingSystem(environment, logConfig, logFile); setLogLevels(applicationContext, environment); handleIncludedProfiles(environment); } } private void insertPropertySources (MutablePropertySources propertySources, CompositePropertySource composite) { MutablePropertySources incoming = new MutablePropertySources(); incoming.addFirst(composite); PropertySourceBootstrapProperties remoteProperties = new PropertySourceBootstrapProperties(); Binder.get(environment(incoming)).bind("spring.cloud.config" , Bindable.ofInstance(remoteProperties)); if (! remoteProperties.isAllowOverride() || (!remoteProperties.isOverrideNone() && remoteProperties.isOverrideSystemProperties())) { propertySources.addFirst(composite); return ; } if (remoteProperties.isOverrideNone()) { propertySources.addLast(composite); return ; } if (propertySources .contains(StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME)) { if (!remoteProperties.isOverrideSystemProperties()) { propertySources.addAfter( StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, composite); } else { propertySources.addBefore( StandardEnvironment.SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, composite); } } else { propertySources.addLast(composite); } } }
上述的代码就涉及两点:
一个是通过PropertySourceLocator 接口加载外部配置
一个是用于解析以spring.cloud.config 为开头的PropertySourceBootstrapProperties 属性,默认情况下,外部配置比内部变量有更高的优先级。
2.3.2、ConfigurationPropertiesRebinderAutoConfiguration 通过命名便会发现其跟刷新属性的功能有关,先看下其类结构:
1 2 3 4 5 6 7 @Configuration (proxyBeanMethods = false )@ConditionalOnBean (ConfigurationPropertiesBindingPostProcessor.class ) public class ConfigurationPropertiesRebinderAutoConfiguration implements ApplicationContextAware , SmartInitializingSingleton { }
上述代码表示其依据于当前类环境存在ConfigurationPropertiesBindingPostProcessor Bean这个bean才会被应用,仔细查阅了下,发现只要有使用到@EnableConfigurationProperties 注解即就会被注册。看来此配置跟ConfigurationProperties 注解也有一定的关联性。
下面看看ConfigurationPropertiesBindingPostProcessor的源码:
ConfigurationPropertiesBindingPostProcessor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 public class ConfigurationPropertiesBindingPostProcessor implements BeanPostProcessor , PriorityOrdered , ApplicationContextAware , InitializingBean { @Override public void afterPropertiesSet () throws Exception { this .registry = (BeanDefinitionRegistry) this .applicationContext.getAutowireCapableBeanFactory(); this .binder = ConfigurationPropertiesBinder.get(this .applicationContext); } @Override public Object postProcessBeforeInitialization (Object bean, String beanName) throws BeansException { bind(ConfigurationPropertiesBean.get(this .applicationContext, bean, beanName)); return bean; } private void bind (ConfigurationPropertiesBean bean) { if (bean == null || hasBoundValueObject(bean.getName())) { return ; } Assert.state(bean.getBindMethod() == BindMethod.JAVA_BEAN, "Cannot bind @ConfigurationProperties for bean '" + bean.getName() + "'. Ensure that @ConstructorBinding has not been applied to regular bean" ); try { this .binder.bind(bean); } catch (Exception ex) { throw new ConfigurationPropertiesBindException(bean, ex); } } }
本文就罗列笔者比较关注的几个地方
1.ConfigurationPropertiesRebinder对象的创建
1 2 3 4 5 6 7 8 @Bean @ConditionalOnMissingBean (search = SearchStrategy.CURRENT)public ConfigurationPropertiesRebinder configurationPropertiesRebinder ( ConfigurationPropertiesBeans beans) { ConfigurationPropertiesRebinder rebinder = new ConfigurationPropertiesRebinder( beans); return rebinder; }
此类读者可以自行翻阅代码,可以发现其暴露了JMX接口以及监听了springcloud context自定义的EnvironmentChangeEvent 事件。看来其主要用来刷新ApplicationContext上的beans(含@ConfigurationProperties注解 )对象集合
2.ConfigurationPropertiesBeans对象的创建
BeanPostProcessor将PropertySources绑定到使用@ConfigurationProperties注释的bean。
1 2 3 4 5 @Bean @ConditionalOnMissingBean (search = SearchStrategy.CURRENT)public ConfigurationPropertiesBeans configurationPropertiesBeans () { return new ConfigurationPropertiesBeans(); }
配合@ConfigurationProperties注解,其会缓存ApplicationContext上的所有含有ConfigurationProperties 注解的bean。与第一点所提的ConfigurationPropertiesRebinder 对象搭配使用
3.实例化结束后刷新父级ApplicationContext上的属性
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Override public void afterSingletonsInstantiated () { if (this .context.getParent() != null ) { ConfigurationPropertiesRebinder rebinder = this .context .getBean(ConfigurationPropertiesRebinder.class ) ; for (String name : this .context.getParent().getBeanDefinitionNames()) { rebinder.rebind(name); } } }
2.3.3、EncryptionBootstrapConfiguration 与属性读取的加解密有关,跟JDK的keystore也有一定的关联,具体就不去解析了。读者可自行分析
3、Bean加载问题 此处本文插入这个Bean的加载问题,因为笔者发现SpringApplication会被调用两次,那么ApplicationContext实例也会被创建两次。那么基于@Configuration 修饰过的自定义的Bean是不是也会被加载两次呢??
经过在cloud环境下编写了一个简单的Bean
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 package com.example.clouddemo;import org.springframework.beans.BeansException;import org.springframework.context.ApplicationContext;import org.springframework.context.ApplicationContextAware;import org.springframework.context.annotation.Configuration;@Configuration public class TestApplication implements ApplicationContextAware { @Override public void setApplicationContext (ApplicationContext applicationContext) throws BeansException { System.out.println("application parent context id: " + applicationContext.getParent().getId()); System.out.println("application context id: " + applicationContext.getId()); } }
且在application.properties文件中指定spring.application.name=springChild以及bootstrap.properties文件中也指定 spring.application.name=springBootstrap
运行main方法之后打印的关键信息如下:
1 2 application parent context id: bootstrap application context id: springBootstrap-1
经过在org.springframework.boot.context.ContextIdApplicationContextInitializer 类上进行断点调试发现只有用户级ApplicationContext被创建的过程中会实例化用户自定义Bean 。也就是说bootstrapContext并不会去实例化用户自定义的Bean ,这样就很安全。
那么为何如此呢???
其实很简单,因为bootstrapContext指定的source类只有BootstrapImportSelectorConfiguration,并没有用户编写的启动类,也就无法影响用户级别Context的Bean加载实例化了,并且该类上无@EnableAutoConfiguration、@ComponentScan注解,表明其也不会去扫描我们自己的包以及处理spring.factories文件中@EnableAutoConfiguration注解key对应的配置集合 , 而用户自己编写的启动类上都会有@SpringBootApplication注解,该注解是一个复合注解,里面会引入了@EnableAutoConfiguration、@ComponentScan、@SpringBootConfiguration,它会扫描我们自己的包以及处理spring.factories文件中@EnableAutoConfiguration注解key对应的配置集合。